package cn.tml.innermost.music.service.impl;

import cn.tml.innermost.framework.cache.impl.RedisCache;
import cn.tml.innermost.framework.entity.enums.ResultCode;
import cn.tml.innermost.framework.exception.ServiceException;
import cn.tml.innermost.framework.utils.StringUtils;
import cn.tml.innermost.music.dos.*;
import cn.tml.innermost.music.params.HomaPageParams;
import cn.tml.innermost.music.params.MusicParams;
import cn.tml.innermost.music.params.MusicSearchParams;
import cn.tml.innermost.music.params.SelectMusicWithKeyParams;
import cn.tml.innermost.music.vo.*;
import cn.tml.innermost.music.mapper.MusicMapper;
import cn.tml.innermost.music.service.AlbumService;
import cn.tml.innermost.music.service.MusicService;
import cn.tml.innermost.music.service.MusicListService;
import cn.tml.innermost.music.service.SingerService;
import cn.tml.innermost.music.utils.OssUtil;
import cn.tml.innermost.music.utils.ThreadDownloader;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * <p>
 * 服务实现类
 * </p>
 *
 * @author 燧枫
 * @since 2022-10-18
 */
@Service
public class MusicServiceImpl extends ServiceImpl<MusicMapper, Music> implements MusicService {

    // 每次增加最大播放量
    private static final int MUSIC_PLAYS_MAX = 60;

    // 歌曲的redsi前缀
    private static final String MUSIC_MUSIC = "music-music:";

    @Resource
    private RedisTemplate redisTemplate;

    @Autowired
    private MusicMapper musicMapper;

    @Resource
    private RedisCache redisCache;

    @Autowired
    private SingerService singerService;

    @Autowired
    private MusicListService musicListService;

    @Autowired
    private AlbumService albumService;

    @Autowired
    MusicUrlService musicUrlService;

    /**
     * 根据id获取歌曲
     *
     * @param musicId
     * @return: MusicInfoVO
     */
    @Override
    public MusicInfoVO getMusic(Long musicId) {
        Music music = null;
//        先判断是否在redis中
        if (redisTemplate.hasKey(MUSIC_MUSIC + musicId)) {
//        如果在redis中, 直接得到歌曲实体类
            music = (Music) redisTemplate.opsForValue().get(MUSIC_MUSIC + musicId);
        } else {
//        不在, 根据歌曲id得到歌曲实体类
            music = this.getById(musicId);
//        如果查不到，直接抛异常
            if (music == null)
                throw new ServiceException(ResultCode.MUSIC_GET_FAIL);
//        如果删除标记为true，直接抛异常
            if (music.getDeleteFlag() == true)
                throw new ServiceException(ResultCode.MUSIC_GET_FAIL);
//        保存至redis中
            redisTemplate.opsForValue().set(MUSIC_MUSIC + musicId, music, 60 * 60, TimeUnit.SECONDS);
        }
//        转为MusicVO并返回
        return MusicInfoVO.valueOf(music);
    }

    @Override
    public List<MusicInfoVO> selectMusicList(List<Long> musicIdList) {
        List<MusicInfoVO> result = new ArrayList<>();

        for (Long musicId : musicIdList) {
            QueryWrapper<Music> queryWrapper = new QueryWrapper<>();
            queryWrapper.eq("id",musicId);
            Music music = musicMapper.selectOne(queryWrapper);
            result.add(MusicInfoVO.valueOf(music));
        }
        return result;
    }

    @Override
    public List<MusicInfoVO> selectMusicListWithColumn(SelectMusicWithKeyParams params) {
        String column = params.getColumn();
        String value = params.getValue();
        Integer nums = params.getNums();
        QueryWrapper<Music> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq(column,value).orderByDesc("plays_nums").last("LIMIT "+nums);
        List<Music> musicList = musicMapper.selectList(queryWrapper);
        List<MusicInfoVO> result = new ArrayList<>();
        for (Music music : musicList) {
            result.add(MusicInfoVO.valueOf(music));
        }
        return result;
    }

    /***
     * 分页获取所有歌曲
     * @param current
     * @param size
     * @return: MusicPageVO
     */
    public MusicPageVO getAllMusic(Long current, Long size) {
//        限制一波分页大小（20）
        if (size > 20)
            throw new ServiceException(ResultCode.PAGE_TOO_BIG);
        MusicPageVO musicPageVO = null;
        List<Long> musicList = new ArrayList<>();
        List<MusicInfoVO> musicInfoVOList = new ArrayList<>();
//        先判断是否在redis中
        if (redisTemplate.hasKey(MUSIC_MUSIC + current + "&" + size)) {
            musicList = redisTemplate.opsForList().
                    range(MUSIC_MUSIC + current + "&" + size, 0, -1);
//        通过list取出redis中所有实体类
            for (int i = 0; i < musicList.size(); i++) {
//        判断实体类是否在redis中
                if (redisTemplate.hasKey(MUSIC_MUSIC + musicList.get(i))) {
                    musicInfoVOList.add(MusicInfoVO.valueOf((Music) redisTemplate.opsForValue()
                            .get(MUSIC_MUSIC + musicList.get(i))));
//        加入被遗忘的歌
                }
            }
//        再取出PageVO,并封装成musicPageVO
            if (redisTemplate.hasKey(MUSIC_MUSIC + current + "&" + size + "pageOV")) {
                musicPageVO = new MusicPageVO((PageOV) redisTemplate.opsForValue()
                        .get(MUSIC_MUSIC + current + "&" + size + "pageOV"), musicInfoVOList);
            }
//        不在redis中
        } else {
//        根据current与size直接查询
            Page<Music> page = new Page<>(current, size);
            this.getBaseMapper().selectPage(page, new QueryWrapper<Music>().eq("delete_flag", 0));
//        提取PageOV所需信息并封装
            PageOV pageOV = new PageOV();
            pageOV.setCurrent(current);
            pageOV.setSize(size);
            pageOV.setTotal(page.getTotal());
            pageOV.setPages(page.getPages());
//        将pageOV保存到redis中
            redisTemplate.opsForValue().
                    set(MUSIC_MUSIC + current + "&" + size + "pageOV", pageOV, 60 * 60, TimeUnit.SECONDS);
//        将提取的所有Music实体类转换成MusicVO并封装到List中
            for (int i = 0; i < page.getRecords().size(); i++) {
//        将所有获取歌曲的实体类保存到redis中，将歌曲id封装成list
                Music music = page.getRecords().get(i);
                musicList.add(music.getId());
                redisTemplate.opsForValue().
                        set(MUSIC_MUSIC + music.getId(), music, 60 * 60, TimeUnit.SECONDS);
                musicInfoVOList.add(MusicInfoVO.valueOf(music));
            }
            musicPageVO = new MusicPageVO(pageOV, musicInfoVOList);
//        将list保存至redis中
            redisTemplate.opsForList().rightPushAll(MUSIC_MUSIC + current + "&" + size, musicList);
            redisTemplate.expire(MUSIC_MUSIC + current + "&" + size, 60 * 60, TimeUnit.SECONDS);
        }
        return musicPageVO;
    }

    /***
     * 查询实现类
     * @param musicSearchParams
     * @return: OK
     */
    @Override
    public List<MusicInfoVO> search(MusicSearchParams musicSearchParams) {
        // 分页起始页
        int startPage = musicSearchParams.getPageIndex();
        // 结果集数量
        int resultNums = musicSearchParams.getResultNums();
        // 查询关键字
        String keyWords = musicSearchParams.getKeyWords();
        // plus分页查询类
        Page<Music> page = new Page<>(startPage, resultNums);

        // 查询数据集
        List<MusicBaseInfoVO> musicBaseInfoVOList = new ArrayList<>();
        // 如果查询为歌手名，则返回该歌手的所有歌曲
        QueryWrapper<Singer> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("name", keyWords);
        List<Singer> list = singerService.list(queryWrapper);
        if (list.size() != 0) {
            this.getBaseMapper().selectPage(page, new QueryWrapper<Music>().like(StringUtils.isNotEmpty(keyWords), "singer_name", keyWords));
        } else {
            this.getBaseMapper().selectPage(page, new QueryWrapper<Music>().select("id","name","singer_name","cover_url").like(StringUtils.isNotEmpty(keyWords),"name", keyWords+"%"));
        }
        List<Music> records = page.getRecords();
        List<MusicInfoVO> res = new ArrayList<>();
        for (Music music : records) {
           res.add(MusicInfoVO.valueOf(music));
        }
        return res;
    }


    /***
     * 增加歌曲的播放量
     * @param musicId
     * @return: OK
     */
    public void addMusicPlayNums(Long musicId) {
        Music music = this.getById(musicId);
        if(music == null || music.getDeleteFlag())
            throw new ServiceException(ResultCode.MUSIC_NOT_EXIST);
        music.setPlaysNums(music.getPlaysNums() + 1);
        if (!updateById(music))
            throw new ServiceException(ResultCode.MUSIC_UPDATE_FAIL);
    }

    /**
     * 新建一首歌曲
     *
     * @param musicParams
     * @return: MusicInfoVO
     */
    @Override
    public MusicInfoVO addMusic(MusicParams musicParams) {
//        先将musicParams转为实体类
        Music music = Music.valueOf(musicParams);
//        将实体类插入数据库中，如果失败，抛异常
        if (!this.save(music))
            throw new ServiceException(ResultCode.MUSIC_SAVE_FAIL);
//        如果成功，初始化参数，转为MusicVO并返回
        music.setPlaysNums(0);
        music.setCollectionNums(0);
//        将爬下来的歌转为oss的链接
        try {
            music.setMusicUrl(OssUtil.saveToOss(ThreadDownloader.urlGetFile(music.getMusicUrl()), music.getId().toString() + ".mp3"));
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        if (!this.updateById(music))
            throw new ServiceException(ResultCode.MUSIC_UPDATE_FAIL);
        return MusicInfoVO.valueOf(music);
    }

    /**
     * 删除一首歌曲
     *
     * @param musicId
     * @return: MusicInfoVO
     */
    @Override
    public MusicInfoVO delMusic(Long musicId) {
//        根据歌曲id得到歌曲实体类
        Music music = this.getById(musicId);
//        如果查不到，直接抛异常
        if (music == null)
            throw new ServiceException(ResultCode.MUSIC_GET_FAIL);
//        将删除标记至为true，并更新至数据库中，失败，抛异常
        music.setDeleteFlag(true);
        if (!updateById(music))
            throw new ServiceException(ResultCode.MUSIC_UPDATE_FAIL);
//        将redsi中的music对象直接删除
        if (redisTemplate.hasKey(MUSIC_MUSIC + musicId)) {
            if (!redisTemplate.delete(MUSIC_MUSIC + musicId))
                throw new ServiceException(ResultCode.MUSIC_UPDATE_FAIL);
        }
//        如果成功，转为MusicVO并返回
        return MusicInfoVO.valueOf(music);
    }

    /**
     * 修改歌曲信息
     *
     * @param musicId
     * @param musicParams
     * @return: MusicInfoVO
     */
    @Override
    public MusicInfoVO updateMusic(Long musicId, MusicParams musicParams) {
//        根据歌曲id得到歌曲实体类
        Music music = this.getById(musicId);
//        如果查不到，直接抛异常
        if (music == null)
            throw new ServiceException(ResultCode.MUSIC_GET_FAIL);
//        如果删除标记为true，直接抛异常
        if (music.getDeleteFlag() == true)
            throw new ServiceException(ResultCode.MUSIC_GET_FAIL);
//        将实体类同步至musicParams，并更新至数据库中
        music.setId(musicId);
        music.setName(musicParams.getName());
        music.setSingerName(musicParams.getSingerName());
        music.setAlbumName(musicParams.getAlbumName());
        music.setLanguages(musicParams.getLanguages());
        music.setGenres(musicParams.getGenres());
        music.setCoverUrl(musicParams.getCoverUrl());
        music.setMusicUrl(musicParams.getMusicUrl());
        music.setLyrics(musicParams.getLyrics());
        music.setDescription(musicParams.getDescription());
        if (!updateById(music))
            throw new ServiceException(ResultCode.MUSIC_UPDATE_FAIL);
//        查看是否在redis中
        if (redisTemplate.hasKey(MUSIC_MUSIC + musicId)) {
//        如果在redis中, 先将将redsi中的music对象删除
            if (!redisTemplate.delete(MUSIC_MUSIC + musicId))
                throw new ServiceException(ResultCode.MUSIC_UPDATE_FAIL);
//        再将新的对象放进redis中
            redisTemplate.opsForValue().set(MUSIC_MUSIC + musicId, music, 60 * 60, TimeUnit.SECONDS);
        }
//       如果成功，转为MusicVO并返回
        return MusicInfoVO.valueOf(music);
    }

    /**
     * 根据id获取音乐链接
     * @param id
     * @return
     */
    @Override
    public MusicInfoVO getMusicUrl(Long id) {
        Music music = musicMapper.selectById(id);
        String songMid = music.getMusicMid();
        String musicUrl = musicUrlService.getMusicUrlBySongMid(songMid);
        music.setMusicUrl(musicUrl);
        return MusicInfoVO.valueOf(music);
    }

    /**
     * 主页推荐
     * @return
     */
    @Override
    public HomePageVO getHomePage(HomaPageParams homaPageParams) {
        checkHomePageParams(homaPageParams);

        int musicNums = homaPageParams.getMusicNums();
        int albumNums = homaPageParams.getAlbumNums();
        int singerNums = homaPageParams.getSingerNums();
        int musicListNums = homaPageParams.getMusicListNums();

        List<MusicInfoVO> randomMusicList = new ArrayList<>();
        List<AlbumInfoVO> randomAlbumList = new ArrayList<>();
        List<SingerInfoVO> randomSingerList = new ArrayList<>();
        List<MusicListInfoVO> randomMusicListList = new ArrayList<>();

        if (musicNums != 0)  randomMusicList = getRandomMusicFromTop10000(musicNums);
        if (albumNums != 0)  randomAlbumList = albumService.getRandomAlbumsFromTop1000(albumNums);
        if (singerNums != 0) randomSingerList = singerService.getRandomSingerFromTop100(singerNums);
        if (musicListNums != 0)  randomMusicListList = musicListService.getRandomMusicListFromTop1000(musicListNums);

        return new HomePageVO(randomMusicList, randomAlbumList, randomSingerList, randomMusicListList);
    }

    /**
     * 此更新方法只会在用户模块的定时任务中触发，定期每天0点进行更新，因此不会有并发问题
     * @param musicId
     * @param count
     * @return
     */

    @Override
    public MusicInfoVO incrMusicListenCount(Long musicId, int count) {
        QueryWrapper<Music> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("id",musicId);
        Music music = musicMapper.selectOne(queryWrapper);
        music.setPlaysNums(music.getPlaysNums()+count);
        musicMapper.updateById(music);
        return MusicInfoVO.valueOf(music);
    }

    @Override
    public String getMusicLyrics(Long musicId) {
        QueryWrapper<Music> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("id",musicId);
        Music music = musicMapper.selectOne(queryWrapper);
        return music.getLyrics();
    }

    /**
     * 随机获取redis中的前10000评论数的歌
     */
    public List<MusicInfoVO> getRandomMusicFromTop10000(int count) {
        try {
            if (redisTemplate.hasKey(MUSIC_MUSIC + "top10000Music")) {
                Set<String> randomMusicJsonSet = redisTemplate.opsForSet().distinctRandomMembers(MUSIC_MUSIC + "top10000Music", count);
                List<MusicInfoVO> randomMusicList = new ArrayList<>();
                ObjectMapper objectMapper = new ObjectMapper();
                for (String musicJson : randomMusicJsonSet) {
                    MusicInfoVO musicInfoVO = objectMapper.readValue(musicJson, MusicInfoVO.class);
                    randomMusicList.add(musicInfoVO);
                }
                return randomMusicList;
            } else {
                addTop10000Toredis();
                return getRandomMusicFromTop10000(count);
            }
        } catch (JsonProcessingException e) {
            e.printStackTrace();
            throw new ServiceException(ResultCode.MUSIC_TOP10000_FAIL);
        }
    }

    /**
     * 获取评论数前1万的歌曲,存入redis中
     */
    public void addTop10000Toredis() throws JsonProcessingException {
        List<Music> top10000Music = musicMapper.getTop10000();
        for (Music music : top10000Music) {
            MusicInfoVO musicInfoVO = MusicInfoVO.valueOf(music);
            String musicJson = new ObjectMapper().writeValueAsString(musicInfoVO);
            redisTemplate.opsForSet().add(MUSIC_MUSIC + "top10000Music", musicJson);
        }
    }

    /**
     * 参数校验
     * @param homaPageParams
     */
    private void checkHomePageParams(HomaPageParams homaPageParams) {
        int musicNums = homaPageParams.getMusicNums();
        int albumNums = homaPageParams.getAlbumNums();
        int singerNums = homaPageParams.getSingerNums();
        int MusicListNums = homaPageParams.getMusicListNums();
        if (musicNums < 0 || musicNums > 20 || albumNums < 0 || albumNums > 20 || singerNums < 0
        || singerNums > 20 || MusicListNums < 0 || MusicListNums > 20) {
            throw new ServiceException(ResultCode.MUSIC_HOMEPAGE_PARAMS_FAIL);
        }
    }
}
